package com.ikingtech.platform.service.pay.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ikingtech.framework.sdk.base.model.BatchParam;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.enums.pay.PayStatusEnum;
import com.ikingtech.framework.sdk.enums.pay.PayTypeEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.pay.api.PayApi;
import com.ikingtech.framework.sdk.pay.embedded.CashierBuilder;
import com.ikingtech.framework.sdk.pay.embedded.supplier.*;
import com.ikingtech.framework.sdk.pay.model.*;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.pay.entity.CashierSupplierDO;
import com.ikingtech.platform.service.pay.entity.PayRecordDO;
import com.ikingtech.platform.service.pay.exception.PayExceptionInfo;
import com.ikingtech.platform.service.pay.service.CashierSupplierRepository;
import com.ikingtech.platform.service.pay.service.PayRecordRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/pay", name = "支付中心", description = "支付中心")
public class PayController implements PayApi {

    private final CashierBuilder cashierBuilder;

    private final PayRecordRepository recordRepo;

    private final CashierSupplierRepository supplierRepo;

    /**
     * 支付下单
     * @param payment 支付信息
     * @return 支付结果
     */
    @Override
    @OperationLog(value = "支付下单")
    @Transactional(rollbackFor = Exception.class)
    public R<PayOrderDTO> order(PaymentDTO payment) {
        // 加载配置信息
        CashierSupplierConfig config = this.loadConfig(payment.getSupplierId());
        // 查询支付记录
        PayRecordDO payingEntity = this.recordRepo.getOne(Wrappers.<PayRecordDO>lambdaQuery().eq(PayRecordDO::getRequestId, payment.getRequestId()).in(PayRecordDO::getStatus, PayStatusEnum.PAYING.name()));
        if (null != payingEntity) {
            // 如果支付记录存在，则返回支付结果
            return R.ok(Tools.Json.toBean(payingEntity.getProps(), PayOrderDTO.class));
        }
        // 如果支付记录不存在，则创建新的支付记录
        PayRecordDO entity = Tools.Bean.copy(payment, PayRecordDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setSupplierType(config.getType().name());
        entity.setStatus(PayStatusEnum.PAYING.name());
        // 构建支付订单
        PayOrder payOrder = this.cashierBuilder.build(config)
                .pay(PayArgs.builder()
                        .payType(payment.getPayType())
                        .amount(payment.getAmount())
                        .requestId(payment.getRequestId())
                        .subject(payment.getTitle())
                        .channelUserId(payment.getChannelUserId())
                        .channelAppId(payment.getChannelAppId())
                        .option(payment.getOption())
                        .build()
                );
        // 保存支付记录
        entity.setChannelTradeId(payOrder.getChannelTradeId());
        entity.setSupplierTradeId(payOrder.getSupplierTradeId());
        entity.setProps(Tools.Json.toJsonStr(payOrder));
        this.recordRepo.save(entity);
        // 返回支付结果
        return R.ok(Tools.Bean.copy(payOrder, PayOrderDTO.class));
    }

    /**
     * 退款
     * @param refundParam 退款参数
     * @return 退款结果
     */
    @Override
    @OperationLog(value = "退款")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> refund(PayRefundParamDTO refundParam) {
        // 根据请求ID和状态查询支付记录
        PayRecordDO entity = this.recordRepo.getOne(Wrappers.<PayRecordDO>lambdaQuery().eq(PayRecordDO::getRequestId, refundParam.getRequestId()).in(PayRecordDO::getStatus, PayStatusEnum.PAY_SUCCESS.name()));
        if (null == entity) {
            throw new FrameworkException("successPayRecordNotFound");
        }
        // 构建退款参数
        RefundArgs args = RefundArgs.builder()
                .requestId(entity.getRequestId())
                .refundAmount(refundParam.getAmount())
                .totalAmount(entity.getAmount())
                .build();
        // 调用收银员进行退款
        RefundResult result = this.cashierBuilder
                .build(this.loadConfig(entity.getSupplierId()))
                .refund(args);
        // 更新支付记录状态和原因
        entity.setStatus(Boolean.TRUE.equals(result.getSuccess()) ? PayStatusEnum.REFUNDED.name() : PayStatusEnum.REFUNDED_FAIL.name());
        entity.setCause(result.getCause());
        this.recordRepo.updateById(entity);
        return R.ok();
    }

    /**
     * 取消支付
     * @param cancelParam 取消参数
     * @return 取消结果
     */
    @Override
    @OperationLog(value = "取消支付")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> cancel(PaymentCancelParamDTO cancelParam) {
        // 根据请求ID和状态查询支付记录
        PayRecordDO entity = this.recordRepo.getOne(Wrappers.<PayRecordDO>lambdaQuery().eq(PayRecordDO::getRequestId, cancelParam.getRequestId()).in(PayRecordDO::getStatus, Tools.Coll.newList(PayStatusEnum.PAYING.name(), PayStatusEnum.PAY_SUCCESS.name())));
        if (null == entity) {
            throw new FrameworkException(PayExceptionInfo.PAY_RECORD_NOT_FOUND);
        }
        // 执行支付取消操作
        CancelResult result = this.executePayCancel(entity);
        // 更新支付记录的状态和取消原因
        entity.setStatus(Boolean.TRUE.equals(result.success()) ? PayStatusEnum.PAY_CANCEL.name() : PayStatusEnum.PAY_CANCEL_FAIL.name());
        entity.setCause(result.getCause());
        this.recordRepo.updateById(entity);
        return R.ok();
    }

    /**
     * 发送红包
     * @param redPack 红包信息
     * @return 返回红包发送结果
     */
    @Override
    @OperationLog(value = "发送红包")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> redPack(PayRedPackDTO redPack) {
        // 加载配置信息
        CashierSupplierConfig cashierSupplierConfig = this.loadConfig(redPack.getSupplierId());
        // 复制实体对象
        PayRecordDO entity = Tools.Bean.copy(redPack, PayRecordDO.class);
        // 生成唯一ID
        entity.setId(Tools.Id.uuid());
        // 设置供应商类型
        entity.setSupplierType(cashierSupplierConfig.getType().name());
        // 构建红包参数
        RedPackArgs args = RedPackArgs.builder()
                .requestId(redPack.getRequestId())
                .amount(redPack.getAmount())
                .receiverCount(redPack.getReceiverCount())
                .title(redPack.getTitle())
                .wishingWords(redPack.getWishingWords())
                .channelUserId(redPack.getChannelUserId())
                .remark(redPack.getRemark())
                .build();
        // 发送红包
        RedPackResult result = this.cashierBuilder.build(cashierSupplierConfig).redPack(args);
        // 更新实体对象
        entity.setSupplierTradeId(result.getSupplierTradeId());
        entity.setChannelTradeId(result.getChannelTradeId());
        if (!Boolean.TRUE.equals(result.getSending())) {
            // 红包发送失败
            entity.setStatus(result.success() ? PayStatusEnum.RED_PACK_SUCCESS.name() : PayStatusEnum.RED_PACK_FAIL.name());
            entity.setCause(result.getCause());
        } else {
            // 红包发送中
            entity.setStatus(PayStatusEnum.RED_PACK_SENDING.name());
        }
        entity.setCause(result.getCause());
        entity.setProps(Tools.Json.toJsonStr(args));
        // 保存实体对象
        this.recordRepo.save(entity);
        return R.ok();
    }

    /**
     * 根据请求ID获取支付结果
     * @param requestId 请求ID
     * @return 支付结果
     */
    @Override
    public R<PayRecordDTO> getPayResultByRequestId(String requestId) {
        // 根据请求ID查询支付记录
        PayRecordDO entity = this.recordRepo.getOne(Wrappers.<PayRecordDO>lambdaQuery().eq(PayRecordDO::getRequestId, requestId).orderByDesc(PayRecordDO::getUpdateTime));
        // 如果查询不到支付记录，则抛出异常
        if (null == entity) {
            throw new FrameworkException(PayExceptionInfo.PAY_RECORD_NOT_FOUND);
        }
        // 将支付记录转换为支付记录DTO对象
        PayRecordDTO payRecord = this.modelConvert(entity);
        // 返回支付结果
        return R.ok(payRecord);
    }

    /**
     * 根据请求ID列表查询支付结果列表
     * @param requestIds 请求ID列表
     * @return 支付结果列表
     */
    @Override
    public R<List<PayRecordDTO>> listPayResultByRequestIds(BatchParam<String> requestIds) {
        if (Tools.Coll.isBlank(requestIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(Tools.Coll.convertList(this.recordRepo.list(Wrappers.<PayRecordDO>lambdaQuery()
                .in(PayRecordDO::getRequestId, requestIds.getList())
                .orderByDesc(PayRecordDO::getUpdateTime)), this::modelConvert));
    }

    /**
     * 处理支付结果通知
     * @param requestId 请求ID
     * @param payResultBody 支付结果体
     * @param payResultMap 支付结果参数映射
     * @return 返回支付结果响应
     */
    @RequestMapping("/notify/{requestId}")
    @Transactional(rollbackFor = Exception.class)
    public String payResultNotify(@PathVariable String requestId,
                                  @RequestBody(required = false) String payResultBody,
                                  @RequestParam(required = false) Map<String, Object> payResultMap) {
        // 根据请求ID查询支付记录
        PayRecordDO entity = this.recordRepo.getOne(Wrappers.<PayRecordDO>lambdaQuery().eq(PayRecordDO::getRequestId, requestId).eq(PayRecordDO::getStatus, PayStatusEnum.PAYING.name()));
        if (null == entity) {
            return Tools.Str.EMPTY;
        }
        // 根据支付记录构建收银员并验证支付结果
        PayResult result = this.cashierBuilder.build(this.loadConfig(entity.getSupplierId())).validate(payResultBody, payResultMap);
        if (!PayStatusEnum.PAYING.name().equals(entity.getStatus())) {
            return result.getSupplierResponse();
        }
        // 更新支付记录
        this.updatePayRecord(entity, result);
        this.recordRepo.updateById(entity);
        return result.getSupplierResponse();
    }

    /**
     * 定时查询支付结果，每分钟执行一次
     */
    @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES)
    @Transactional(rollbackFor = Exception.class)
    public void pollingPayResult() {
        // 查询正在支付中的记录
        List<PayRecordDO> payingRecordEntities = this.recordRepo.list(Wrappers.<PayRecordDO>lambdaQuery().eq(PayRecordDO::getStatus, PayStatusEnum.PAYING.name()));
        if (Tools.Coll.isNotBlank(payingRecordEntities)) {
            // 遍历正在支付中的记录
            for (PayRecordDO payingRecordEntity : payingRecordEntities) {
                // 如果创建时间距今超过20分钟，则取消支付
                if (LocalDateTime.now().minusMinutes(20).isAfter(payingRecordEntity.getCreateTime())) {
                    // 执行支付取消操作
                    CancelResult result = this.executePayCancel(payingRecordEntity);
                    // 更新支付状态为取消成功或取消失败
                    payingRecordEntity.setStatus(Boolean.TRUE.equals(result.success()) ? PayStatusEnum.PAY_CANCEL.name() : PayStatusEnum.PAY_CANCEL_FAIL.name());
                    payingRecordEntity.setCause(result.getCause());
                } else {
                    // 获取支付结果
                    PayResult result = this.cashierBuilder
                            .build(this.loadConfig(payingRecordEntity.getSupplierId()))
                            .getPayResult(GetPayResultArgs.builder()
                                    .requestId(payingRecordEntity.getRequestId())
                                    .supplierTradeId(payingRecordEntity.getSupplierTradeId())
                                    .channelTradeId(payingRecordEntity.getChannelTradeId())
                                    .build());
                    // 如果支付状态为正在支付，则继续等待
                    if (Boolean.TRUE.equals(result.getPaying())) {
                        continue;
                    }
                    // 更新支付记录
                    this.updatePayRecord(payingRecordEntity, result);
                }
            }
            // 批量更新支付记录状态
            this.recordRepo.updateBatchById(payingRecordEntities);
        }
    }

    /**
     * 定时查询红包发送结果，每小时执行一次
     */
    @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.HOURS)
    @Transactional(rollbackFor = Exception.class)
    public void pollingRedPackResult() {
        // 查询发送中的红包记录
        List<PayRecordDO> sendingRecordEntities = this.recordRepo.list(Wrappers.<PayRecordDO>lambdaQuery().eq(PayRecordDO::getStatus, PayStatusEnum.RED_PACK_SENDING.name()));
        if (Tools.Coll.isNotBlank(sendingRecordEntities)) {
            // 遍历发送中的红包记录
            sendingRecordEntities.forEach(entity -> {
                // 构建请求参数
                RedPackArgs args = RedPackArgs.builder()
                        .requestId(entity.getRequestId())
                        .build();
                // 调用发红包确认发送结果
                RedPackResult result = this.cashierBuilder.build(this.loadConfig(entity.getSupplierId())).readPackConfirm(args);
                // 根据发送结果更新红包状态
                if (!Boolean.TRUE.equals(result.getSending())) {
                    entity.setStatus(result.success() ? PayStatusEnum.RED_PACK_SUCCESS.name() : PayStatusEnum.RED_PACK_FAIL.name());
                }
                // 更新原因
                entity.setCause(result.getCause());
            });
            // 更新红包记录
            this.recordRepo.updateBatchById(sendingRecordEntities);
        }
    }

    /**
     * 加载配置
     * @param supplierId 供应商ID
     * @return 配置对象
     */
    private CashierSupplierConfig loadConfig(String supplierId) {
        // 根据供应商ID获取供应商实体
        CashierSupplierDO entity = this.supplierRepo.getById(supplierId);
        if (null == entity) {
            throw new FrameworkException("cashierSupplierNotFound");
        }
        // 如果实体为空，则从属性中获取配置对象
        return Tools.Bean.copy(entity, CashierSupplierConfig.class);
    }

    /**
     * 执行支付取消操作
     * @param entity 支付记录实体
     * @return 取消结果
     */
    private CancelResult executePayCancel(PayRecordDO entity) {
        return this.cashierBuilder
                .build(this.loadConfig(entity.getSupplierId()))
                .cancel(CancelArgs.builder()
                        .requestId(entity.getRequestId())
                        .amount(entity.getAmount())
                        .payType(PayTypeEnum.valueOf(entity.getPayType()))
                        .build());
    }

    /**
     * 更新支付记录
     * @param entity 支付记录实体
     * @param result 支付结果
     */
    private void updatePayRecord(PayRecordDO entity, PayResult result) {
        // 设置供应商交易ID
        entity.setSupplierTradeId(result.getSupplierTradeId());
        // 设置渠道交易ID
        entity.setChannelTradeId(result.getChannelTradeId());
        // 设置交易时间
        entity.setTradeTime(result.getTradeTime());
        // 根据支付结果设置状态
        entity.setStatus(Boolean.TRUE.equals(result.getSuccess()) ? PayStatusEnum.PAY_SUCCESS.name() : PayStatusEnum.PAY_FAILED.name());
        // 设置原因
        entity.setCause(result.getCause());
    }

    /**
     * 将实体转换为DTO对象
     * @param entity 实体对象
     * @return DTO对象
     */
    private PayRecordDTO modelConvert(PayRecordDO entity) {
        PayRecordDTO payRecord = Tools.Bean.copy(entity, PayRecordDTO.class);
        if (null != payRecord.getSupplierType()) {
            payRecord.setSupplierTypeName(payRecord.getSupplierType().description);
        }
        if (null != payRecord.getPayType()) {
            payRecord.setPayTypeName(payRecord.getPayType().description);
        }
        if (null != payRecord.getStatus()) {
            payRecord.setStatusName(payRecord.getStatus().description);
        }
        return payRecord;
    }

}
